import {useState,useCallback,useRef,useEffect} from 'react'
import debounce from 'lodash.debounce';
import throttle from 'lodash.throttle';
import request from '@/utils/request';
import usePersistFn from './usePersistFn'
import useUpdateEffect  from './useUpdateEffect'
// 详情参照 https://ahooks.gitee.io/zh-CN/hooks/async

class Fetch {
  // 计数器
  count=1;
  that = this;
  // 状态值
  state={
    loading: false,
    params: [] ,
    data: undefined,
    error: undefined,
    run: this.run.bind(this.that),
    refresh: this.refresh.bind(this.that),
    cancel: this.cancel.bind(this.that),
    unmount: this.unmount.bind(this.that),
  }
  // 延时显示loading的定时器
  loadingDelayTimer=undefined;
  // 配置
  config={}
  // 防抖函数
  debounceRun=undefined;
  // 节流函数
  throttleRun=undefined;
  // 异步任务
  service=undefined;
  // 订阅state值变化
  subscribe=undefined;
  // 协助时订阅
  unsubscribe=[];
  // 修改state
  setState(s = {}) {
    this.state = {
      ...this.state,
      ...s,
    };
    this.subscribe(this.state);
  }
  constructor(service,config,subscribe,initState){
    this.service=service;
    this.config=config;
    this.subscribe=subscribe;
    if(initState){
      this.setState({
        ...this.state,
        ...initState
      })
    }
    this.debounceRun = this.config.debounceInterval
    ? debounce(this._run, this.config.debounceInterval)
    : undefined;
    this.throttleRun = this.config.throttleInterval
      ? throttle(this._run, this.config.throttleInterval)
      : undefined;
  }
  // 内部运行函数
  _run(...args){
    // 取消 loadingDelayTimer
    if (this.loadingDelayTimer) {
      clearTimeout(this.loadingDelayTimer);
    }
    this.count+=1;
    const currentCount=this.count;
    this.setState({
      loading:!this.config.loadingDelay,
      params: args,
    })
    if (this.config.loadingDelay) {
      this.loadingDelayTimer = setTimeout(() => {
        this.setState({
          loading:true,
        });
      }, this.config.loadingDelay);
    }
    return this.service(...args)
      .then((res)=>{
        // 取消请求
        if(currentCount !==this.count){
          return new Promise(() => {});
        }
        // 清楚定时器
        if (this.loadingDelayTimer) {
          clearTimeout(this.loadingDelayTimer);
        }
        const formattedResult = this.config.formatResult ? this.config.formatResult(res) : res;
        
        this.setState({
          data: formattedResult,
          error: undefined,
          loading: false,
        });
        if (this.config.onSuccess) {
          this.config.onSuccess(formattedResult, args);
        }
        return formattedResult;
      })
      .catch((err)=>{
        // 取消请求
        if(currentCount !==this.count){
          return new Promise(() => {});
        }
         // 清楚定时器
        if (this.loadingDelayTimer) {
          clearTimeout(this.loadingDelayTimer);
        }
        this.setState({
          loading:false,
          data:undefined,
          error:err
        })
        if(this.config.onError){
          this.config.onError(err,args)
        }
        if (this.config.throwOnError) {
          throw error;
        }
        console.error(err);
        return Promise.reject(
          'useRequest has caught the exception, if you need to handle the exception yourself, you can set options.throwOnError to true.',
        );
      })
      .finally(()=>{

      })
  }
  // 外部可调用函数
  run(...args) {
    if (this.debounceRun) {
      this.debounceRun(...args);
      //如果 options 存在 debounceInterval，或 throttleInterval，则 run 和 refresh 不会返回 Promise。 带类型需要修复后，此处变成 return;。
      return Promise.resolve(null);
    }
    if (this.throttleRun) {
      this.throttleRun(...args);
      return Promise.resolve(null);
    }
    return this._run(...args);
  }
  // 取消请求
  cancel(){
    if (this.debounceRun) {
      this.debounceRun.cancel();
    }
    if (this.throttleRun) {
      this.throttleRun.cancel();
    }
    if (this.loadingDelayTimer) {
      clearTimeout(this.loadingDelayTimer);
    }
    this.count += 1;
    this.setState({
      loading: false,
    });
  }
  // 刷新请求
  refresh() {
    return this.run(...this.state.params);
  }

  // 卸载时
  unmount() {
    this.cancel();
  }

}

const DEFAULT_KEY = 'USE_REQUEST_DEFAULT_KEY';

/**
 * @method useAsync
 * @description 处理fetch请求
 * @param {Promise} service promise函数
 * @param {object} option 传递参数
 * 
 **/


function useAsync(service,options) {
  const _options = options || {};
  const {
    refreshDeps = [],// 依赖项
    manual = false,// 是否自动触发
    onSuccess = () => {},
    onError = () => {},

    defaultLoading = false,//默认loading状态
    loadingDelay, // 可以延迟 loading 变成 true 的时间，有效防止闪烁

    fetchKey,
    defaultParams = [],// 默认参数
    debounceInterval,
    throttleInterval,
    initialData,// 初始化结果值
    ready = true,
    throwOnError = false,
  } = _options;

  const newstFetchKey = useRef(DEFAULT_KEY);

  // 持久化一些函数
  const servicePersist = usePersistFn(service);
  const onSuccessPersist = usePersistFn(onSuccess);
  const onErrorPersist = usePersistFn(onError);
  // 持久化并行请求获取fetchkey的函数
  const fetchKeyPersist = usePersistFn(fetchKey);
  // 结果转化函数
  let formatResult;
  if ('formatResult' in _options) {
    // eslint-disable-next-line prefer-destructuring
    formatResult = _options.formatResult;
  }
  // 请求集合
  const [fetches, setFetches] = useState({});
  const fetchesRef = useRef(fetches);
  fetchesRef.current = fetches;
  const formatResultPersist = usePersistFn(formatResult);
  // 配置项
  const config = {
    formatResult: formatResultPersist,
    onSuccess: onSuccessPersist,
    onError: onErrorPersist,
    loadingDelay,// loading延时时间 (请求时间过短时的闪屏)
    debounceInterval,
    throttleInterval,
    throwOnError,
  };
  const subscribe = usePersistFn((key, data) => {
    setFetches((s) => {
      // eslint-disable-next-line no-param-reassign
      s[key] = data;
      return { ...s };
    });
  }) ;
  // 存储函数参数
  const readyMemoryParams = useRef();
  // 调用执行函数
  const run =useCallback(
    (...args) => {
      if (!ready) {
        // 没有 ready, 记录请求参数，等 ready 后，发起请求用
        readyMemoryParams.current = args;
        return;
      }

      if (fetchKeyPersist) {
        const key = fetchKeyPersist(...args);
        newstFetchKey.current = key === undefined ? DEFAULT_KEY : key;
      }
      const currentFetchKey = newstFetchKey.current;
     
      let currentFetch = fetchesRef.current[currentFetchKey];
      if (!currentFetch) {
        const newFetch = new Fetch(servicePersist, config,subscribe.bind(null, currentFetchKey),{
          data: initialData,
        });
        currentFetch = newFetch.state;
        setFetches((s) => {
          s[currentFetchKey] = currentFetch;
          return { ...s };
        });
      }
      return currentFetch.run(...args);
    },
    [fetchKey,subscribe, ready],
  )
  const runRef = useRef(run);
  runRef.current = run;  
  // for ready 只有当 options.ready 变为 true 时, 才会发起请求
  const hasTriggeredByReady = useRef(false);
  useUpdateEffect(() => {
    if (ready) {
      if (!hasTriggeredByReady.current && readyMemoryParams.current) {
        runRef.current(...readyMemoryParams.current);
      }
      hasTriggeredByReady.current = true;
    }
  }, [ready]);

    // 第一次默认执行
    useEffect(() => {
      if (!manual) {
        runRef.current(...defaultParams);
      }
    }, []);
  
   //  refreshDeps 变化，重新执行所有请求
   useUpdateEffect(() => {
    if (!manual) {
      /* 全部重新执行 */
      Object.values(fetchesRef.current).forEach((f) => {
        f.refresh();
      });
    }
  }, [...refreshDeps]);
  // 执行时机提示
  const notExecutedWarning = useCallback(
    (name) => () => {
      console.warn(`You should't call ${name} when service not executed once.`);
    },
    [],
  );

  return {
    loading: (ready && !manual)||defaultLoading,
    data: initialData,
    error: undefined,
    params: [],
    cancel: notExecutedWarning('cancel'),
    refresh: notExecutedWarning('refresh'),

    ...((fetches[newstFetchKey.current]) || {}),
    run
  } ;
}

function useRequest(service, options) {
  let promiseService;
  if (typeof service === 'string') {
    promiseService = () => request(service);
  } else if (typeof service === 'object') {
    const { url, ...rest } = service;
    promiseService = () => request(url, rest);
  } else {
    promiseService = (...args) =>
    new Promise((resolve, reject) => {
      const s = service(...args);
      let fn = s;
      if (!s.then) {
        switch (typeof s) {
          case 'string':
            fn = () => request(service);
            break;
          case 'object':
            const { url, ...rest } = s;
            fn =() => request(url, rest);
            break;
        }
      }
      fn.then(resolve).catch(reject);
    });
  }
  return useAsync(promiseService, options);
}

export default useRequest
